﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Threading.Tasks;
using System.Threading;
using IGolovkine.AoA.Diagnostics;

namespace IGolovkine.AoA.Tcp.Channels.Http
{
    public class HttpChannelServer : IMessageChannel, IDisposable
    {
        private readonly Subject<Message> _input;
        private readonly Subject<Message> _output;
        private readonly HttpListener _listener;

        private readonly IDisposable _taskSubscription;
        private readonly IDisposable _inputSubscription;
        private readonly HttpEnvelopeExchangeStrategy _compressionStrategy;
        private readonly MessageAggregator _buffer;

        private readonly HttpChannelServerConfiguration _config;
        private readonly ILogger _logger;

        public HttpChannelServer(HttpChannelServerConfiguration config)
        {
            _logger = ApplicationContext.LogManager.GetClassLogger(this);
            _config = config;

            _input = new Subject<Message>();
            _output = new Subject<Message>();

            _compressionStrategy = new HttpEnvelopeExchangeStrategy();

            _listener = new HttpListener();
            
            foreach(string prefix in _config.Prefixes)
            {
                _logger.Debug("Prefix = '{0}'", prefix);
                _listener.Prefixes.Add(prefix);
            }

            _listener.Start();

            _buffer = new MessageAggregator();
            _inputSubscription = _input.Subscribe(_buffer.Input);

            bool cancelled = false;

            Task task = Task.Factory.StartNew(() =>
               {
                   try
                   {
                       while (cancelled == false)
                       {
                           try
                           {
                               _logger.Debug("Waiting for next request..."); ;
                               HttpListenerContext context = _listener.GetContext();
                               _logger.Trace("Serving request from {0}", context.Request.RemoteEndPoint);
                               HttpEnvelope outgoingMessage = new HttpEnvelope(_buffer.DequeueAll());
                               _logger.Debug("About to do data exchange");
                               this.DoDataExchange(context, outgoingMessage);
                           }
                           catch (Exception ex)
                           {
                               _logger.DebugException("Request error", ex);
                               //_output.OnError(ex);
                           }
                        }
                   }
                   catch (Exception ex)
                   {
                       _logger.DebugException("Server error", ex);
                       _output.OnError(ex);
                   }
                   finally
                   {
                       _logger.Debug("Exiting");
                       _output.OnCompleted();
                   }
               });

            _taskSubscription = Disposable.Create(() =>
                {
                    cancelled = true;
                    _listener.Abort();
                    task.Wait();
                });
        }

        private void DoDataExchange(HttpListenerContext context, HttpEnvelope outgoingMessage)
        {
            HttpEnvelope incomingMessage = _compressionStrategy.Receive(context.Request);
            foreach (Message message in incomingMessage.Messages)
            {
                _output.OnNext(message);
            }
            _compressionStrategy.Send(outgoingMessage, context.Response);
            context.Response.Close();
        }

        public IObservable<Message> Output
        {
            get { return _output; }
        }

        public IObserver<Message> Input
        {
            get { return _input; }
        }

        public void Dispose()
        {
            _inputSubscription.Dispose();
            _taskSubscription.Dispose();
        }
    }
}
